﻿using System;
using System.Collections;
using System.Text;
using System.Linq;

using Antlr.Runtime;
using Netium.Compiler;
using Netium.Compiler.TypeSystem;
using Netium.Compiler.IntermediateRepresentation;

namespace CFrontEnd
{
    partial class CTreeParser
    {
        private ISymbolTableStack _symStack = new SymbolTableStack();

        public override string GetErrorMessage(RecognitionException e, string[] tokenNames)
        {
            var stack = GetRuleInvocationStack();

            string message = null;

            if (e is NoViableAltException)
            {
                var nvae = (NoViableAltException)e;
                message = " no viable alt: token=" + e.Token + " (decision = " + nvae.DecisionNumber + " state " + nvae.StateNumber + ")" + "decision=<<" + nvae.GrammarDecisionDescription + ">>";
            }
            else
            {
                message = base.GetErrorMessage(e, tokenNames);
            }

            message = stack + " " + message;
            System.Diagnostics.Trace.TraceError(message);

            return message;
        }

        private SymbolTable CreateGlobalSymbolTable()
        {
            return new SymbolTable();
        }

        private void AddVariableDefinition(IRToken token, IDataType type)
        {
            var identifier = token.Text;

            if (type == PrimitiveDataTypes.VoidType)
                throw new ArgumentException();

            var symTable = _symStack.Peek();
            if (symTable.Contains(identifier))
                throw new DuplicatedEntryException(identifier);

            var entry = symTable.AllocateEntry(identifier);
            entry.Attributes.SymbolType = SymbolType.Variable;
            entry.Attributes.Type = type;
            token.Symbol = entry;
        }

        private void AddParamDefinition(IRToken token, IDataType type)
        {
            var identifier = token.Text;
            if (type == PrimitiveDataTypes.VoidType)
                throw new ArgumentException();

            var symTable = _symStack.Peek();
            if (symTable.Contains(identifier))
                throw new DuplicatedEntryException(identifier);

            var entry = symTable.AllocateEntry(identifier);
            entry.Attributes.SymbolType = SymbolType.Parameter;
            entry.Attributes.Type = type;
            token.Symbol = entry;
        }

        private void AddFunctionDeclaration(IRToken token, IDataType returnType, ISymbolTable symTable)
        {
            var identifier = token.Text;
            var entry = new Symbol(identifier);
            entry.Attributes.SymbolType = SymbolType.Function;
            entry.Attributes.IsDefined = false;
            entry.Attributes.Type = returnType;
            entry.Attributes.SymbolTable = symTable;

            var currentSymTable = _symStack.Peek();
            var id = currentSymTable.Lookup(identifier);

            if (id != null)
            {
                if (!IsTypeCompatible(id, entry))
                    throw new InvalidCastException();

                token.Symbol = id;
            }
            else
            {
                currentSymTable.AddSymbol(entry);

                token.Symbol = entry;
            }
        }


        private void AddFunctionDefinition(IRToken token, IDataType returnType, ISymbolTable symTable, object code)
        {
            var identifier = token.Text;

            var entry = new Symbol(identifier);
            entry.Attributes.SymbolType = SymbolType.Function;
            entry.Attributes.IsDefined = true;
            entry.Attributes.Type = returnType;
            entry.Attributes.SymbolTable = symTable;
            entry.Attributes.Code = code;

            var currentSymTable = _symStack.Peek();
            var id = currentSymTable.Lookup(identifier);

            if (id != null)
            {
                if (id.Attributes.IsDefined == true)
                    throw new InvalidOperationException();

                if (!IsTypeCompatible(id, entry))
                    throw new InvalidCastException();

                id.Attributes.IsDefined = true;
                id.Attributes.Code = code;

                token.Symbol = id;
            }
            else
            {
                currentSymTable.AddSymbol(entry);

                token.Symbol = entry;
            }
        }

        private bool IsTypeCompatible(ISymbol id, ISymbol entry)
        {
            if (id == null)
                throw new ArgumentNullException("id");
            if (entry == null)
                throw new ArgumentNullException("entry");

            if (id.Attributes.SymbolType == SymbolType.Function)
                return IsFunctionCompatible(id, entry);

            return true;
        }

        private bool IsFunctionCompatible(ISymbol func1, ISymbol func2)
        {
            if (func1.Attributes.SymbolType != SymbolType.Function)
                throw new ArgumentException("Invalid symbol type", "func1");
            if (func2.Attributes.SymbolType != SymbolType.Function)
                throw new ArgumentException("Invalid symbol type", "func2");

            if (func1.Attributes.Type != func2.Attributes.Type)
                return false;

            var func1Params = ((ISymbolTable)func1.Attributes.SymbolTable).Symbols.Where(p => p.Attributes.SymbolType == SymbolType.Parameter).ToArray();
            var func2Params = ((ISymbolTable)func2.Attributes.SymbolTable).Symbols.Where(p => p.Attributes.SymbolType == SymbolType.Parameter).ToArray();

            if (func1Params.Length != func2Params.Length)
                return false;

            for (int i = 0; i < func1Params.Length; i++)
            {
                if (!IsTypeCompatible(func1Params[i], func2Params[i]))
                    return false;
            }

            return true;
        }

    }
}
